#include <pty.h>
#include <sys/fcntl.h>
#include <cstdlib>
#include <poll.h>
#include <mutex>
#include <sys/time.h>
#include <thread>
#include <unistd.h>
#include <signal.h>
#include <wait.h>
#include <string.h>
#include <string>
#include <vector>
#ifndef RpcShellClass
#define RpcShellClass
#define shellExpireMs 10000
class RpcShell
{
private:
    int fd, pid;
    std::recursive_mutex inLock, outLock;
    std::string inBuffer;
    std::string outBuffer;
    struct winsize ws;
    std::string exclusiveId = "";
    long idExpireTime = -1;
    std::thread *pollThread;
    bool quit = false;

    static inline void append(std::string &dest, const char *src, size_t length)
    {
        int pos = dest.size();
        dest.resize(pos + length);
        memcpy(&dest[pos], src, length);
    }
    void nonStopPoll()
    {
        while (!quit)
        {
            usleep(10000);
            struct pollfd p[2] = {{fd, POLLIN, 10}};
            if (!inBuffer.empty())
            {
                p[0].events |= POLLOUT;
            }
            if (p[0].events)
            {
                int pollResult = poll(p, 1, 0);
            }
            if (p[0].revents & POLLIN)
            {
                while (true)
                {
                    char buffer[4096] = {0};
                    int size = read(fd, buffer, sizeof(buffer));
                    fflush(stdout);
                    if (size <= 0)
                        break;
                    else
                    {
                        std::lock_guard<std::recursive_mutex> lk(outLock);
                        outBuffer.append(buffer);
                    }
                }
            }
            if ((!inBuffer.empty()) && (p[0].revents & POLLOUT))
            {
                int res = write(fd, inBuffer.c_str(), inBuffer.size());
                if (res > 0)
                {
                    std::lock_guard<std::recursive_mutex> lk(inLock);
                    inBuffer.erase(0, res);
                }
            }
        }
    }
    static inline long readCurrentMs()
    {
        struct timeval tv;
        struct timezone tz;
        gettimeofday(&tv, &tz);
        return tv.tv_sec * (long)1000 + tv.tv_usec / 1000;
    }
    const bool idKeepAlive(const std::string &id)
    {
        long currentMs = readCurrentMs();
        if (id != exclusiveId)
        {
            printf("id is not exclusiveId! %s %s\n", id.c_str(), exclusiveId.c_str());
            return false;
        }
        else if (idExpireTime + shellExpireMs < currentMs)
        {
            printf("id has expired!\n");
            return false;
        }
        else
        {
            idExpireTime = currentMs;
            return true;
        }
    }

public:
    RpcShell()
    {
        ws.ws_col = 100;
        ws.ws_row = 30;
        pid = forkpty(&fd, nullptr, nullptr, &ws);
        if (!pid)
        {
            static char termstr[] = "TERM=linux";
            putenv(termstr);
            execl(std::getenv("SHELL"), std::getenv("SHELL"), "-l", "-i", nullptr);
        }
        fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | O_NONBLOCK);
        pollThread = new std::thread(&RpcShell::nonStopPoll, this);
        pollThread->detach();
        idExpireTime = -1;
        printf("shell started\n");
    }
    ~RpcShell()
    {
        quit = true;
        delete pollThread;
        kill(pid, SIGTERM);
        close(fd);
        waitpid(pid, nullptr, 0);
    }
    std::string login()
    {
        long currentMs = readCurrentMs();
        if (idExpireTime + shellExpireMs < currentMs)
        {
            exclusiveId = std::to_string(currentMs);
            idExpireTime = currentMs;
            return std::to_string(currentMs);
        }
        else
        {
            return std::string("");
        }
    }
    bool logout(const std::string &id)
    {
        if (id != exclusiveId)
        {
        }
        else
        {
            printf("target %s logout.\n", exclusiveId);
            exclusiveId = "";
        }
    }
    std::string readShell(const std::string &id)
    {
        std::string result;
        if (idKeepAlive(id))
        {

            if (outBuffer.size())
            {
                {
                    std::lock_guard<std::recursive_mutex> lk(outLock);
                    result = std::string(outBuffer.c_str());
                    outBuffer.clear();
                }
                return result;
            }
            else
            {
                return result;
            }
        }
        else
        {
            return result;
        }
    }
    bool writeShell(std::string &data, std::string &id)
    {
        if (idKeepAlive(id))
        {
            std::lock_guard<std::recursive_mutex> lk(inLock);
            inBuffer.append(data);
            return true;
        }
        else
        {
            return false;
        }
    }
};

#endif